package cn.melonlib.apis.support.comp.proxy;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.melonlib.apis.support.annos.ApiModule;
import cn.melonlib.apis.support.annos.ApiParameter;
import cn.melonlib.apis.support.annos.ApiRequest;
import cn.melonlib.apis.support.comp.config.ApiRequestConfig;
import cn.melonlib.apis.support.comp.service.ApiRequestRecordService;
import cn.melonlib.apis.support.enums.ApiRequestActionType;
import cn.melonlib.apis.support.model.bean.ApiModuleConfig;
import cn.melonlib.apis.support.model.entity.ApiRequestRecord;
import cn.melonlib.cores.graphql.comp.utils.GraphQLTools;
import cn.melonlib.cores.identification.comp.messenger.MessengerUtils;
import cn.melonlib.objects.maps.JsonMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Data
public class ApiRequestProxyFactory<T> extends ApplicationObjectSupport implements FactoryBean<T>,InvocationHandler {

    private Class<T> interfaceClass;

    private ApiRequestConfig apiRequestConfig;


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ApplicationContext applicationContext=getApplicationContext();
        ApiRequestRecordService apiRequestRecordService=applicationContext.getBean(ApiRequestRecordService.class);
        GraphQLTools graphQLTools=applicationContext.getBean(GraphQLTools.class);
        MessengerUtils messengerUtils=applicationContext.getBean(MessengerUtils.class);
        ObjectMapper jsonMapper=applicationContext.getBean(ObjectMapper.class);
        ApiModule apiModule=AnnotationUtils.findAnnotation(method.getDeclaringClass(), ApiModule.class);
        ApiRequest apiRequest=AnnotationUtils.findAnnotation(method,ApiRequest.class);
        Class<?> returnType=resultType(method);
        ApiModuleConfig apiModuleConfig=(ApiModuleConfig) applicationContext.getBean(apiModule.apiModuleConfig());
//        String apiUrl=BeanUtil.getProperty(applicationContext.getBean(apiModule.apiModuleConfig().getConfigBeanName()),apiModule.apiModuleConfig().getConfigPropertyName());
        String apiUrl=apiModuleConfig.getUrl();
        String action= Objects.equals(apiRequest.apiRequestAction(), ApiRequestActionType.QUERY)?"QUERY":"MUTATION";
        String funName= StringUtils.defaultIfBlank(apiRequest.name(),method.getName());
        String paramsDeclare=createDeclare(method);
        String paramsInput=createInput(method);

        StringBuilder statement=new StringBuilder(action.toLowerCase()).append(" ").append(funName)
                .append(paramsDeclare).append("{").append(funName).append(paramsInput)
                .append(graphQLTools.createQuery(method))
                .append("}");
        JsonMap variables= Arrays.stream(method.getParameters()).reduce(JsonMap.createMap(),(vars, parameter)->{
            ApiParameter apiParameter=AnnotationUtils.findAnnotation(parameter, ApiParameter.class);
            String paramName= Objects.nonNull(apiParameter)?StringUtils.defaultIfBlank(apiParameter.name(),parameter.getName()):parameter.getName();
            if(Objects.nonNull(args[vars.size()])) {
                vars.append(paramName, args[vars.size()]);
            }
            return vars;
        },(vars,params)->null);
        JsonMap<Object> params=JsonMap.createMap().append("operationName",funName)
                .append("query",statement.toString())
                .append("variables",variables);
//        String url= notificationConfig.getUrl()+"&domain="+domain;
        String domain=messengerUtils.getTenant();
//        if(domain!=null&&Objects.equals(apiModule.)){
//            apiUrl=apiUrl+"&domain="+domain;
//        }
//        JsonMap<String> headers=JsonMap.<String>createMap().append("authorization","Bearer "+);

        ApiRequestRecord record=apiRequestRecordService.sendRequest(apiUrl,statement.toString(),params,action,apiModuleConfig.getName(),domain,funName,apiRequestConfig.createHeaders());
        if(Objects.nonNull(returnType)) {
            if(Collection.class.isAssignableFrom(method.getReturnType())){
                return BeanUtil.copyToList(BeanUtil.getProperty(jsonMapper.readValue(record.getResponse(), Map.class), "data." + funName),returnType);
            }
            return BeanUtil.toBean(BeanUtil.getProperty(jsonMapper.readValue(record.getResponse(), Map.class), "data." + funName), returnType);
        }else{
            return BeanUtil.getProperty(jsonMapper.readValue(record.getResponse(), Map.class), "data." + funName);
        }
    }

    @Override
    public T getObject() throws Exception {
        return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{interfaceClass},this);
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    private String createInput(Method method){
        if(ArrayUtil.isEmpty(method.getParameters())){
            return "";
        }
        return "("+Arrays.stream(method.getParameters()).map(parameter -> {
            ApiParameter apiParameter=AnnotationUtils.findAnnotation(parameter, ApiParameter.class);
            String paramName= Objects.nonNull(apiParameter)?StringUtils.defaultIfBlank(apiParameter.name(),parameter.getName()):parameter.getName();
            return paramName+":$"+paramName;
        }).collect(Collectors.joining(","))+")";
    }

    private String createDeclare(Method method){
        if(ArrayUtil.isEmpty(method.getParameters())){
            return "";
        }
        return "("+Arrays.stream(method.getParameters()).map(parameter -> {
            ApiParameter apiParameter=AnnotationUtils.findAnnotation(parameter, ApiParameter.class);
            String paramName= Objects.nonNull(apiParameter)?StringUtils.defaultIfBlank(apiParameter.name(),parameter.getName()):parameter.getName();
            String typeName=Objects.nonNull(apiParameter)?(StringUtils.defaultString(apiParameter.type(),parameter.getType().getSimpleName())+(apiParameter.required()?"!":"")):parameter.getType().getName();
            return "$"+paramName+":"+typeName;

        }).collect(Collectors.joining(","))+")";
    }

    private Class<?> resultType(Method method){
        ApiRequest apiRequest=AnnotationUtils.findAnnotation(method,ApiRequest.class);
        if(Objects.nonNull(apiRequest)&&!Objects.equals(apiRequest.resultType(),Object.class)){
            Class<?> returnType=apiRequest.resultType();
            if(ClassUtil.isBasicType(returnType)||String.class.isAssignableFrom(returnType)){
                return null;
            }
            return returnType;
        }
        Class<?> returnType=method.getReturnType();
        if(ClassUtil.isBasicType(returnType)||String.class.isAssignableFrom(returnType)){
            return null;
        }
        if(Collection.class.isAssignableFrom(returnType)){
            Class<?> itemType= ResolvableType.forMethodReturnType(method).getGeneric(0).resolve();
            if(ClassUtil.isBasicType(itemType)||String.class.isAssignableFrom(itemType)){
                return null;
            }
            return itemType;
        }
        return returnType;
    }
}
